package com.example.uniquekey;

import lombok.extern.slf4j.Slf4j;

import java.sql.*;
import java.util.concurrent.atomic.AtomicLong;

@Slf4j
public class GeneralKeyGenerator {

    private Keys keys = null;
    private int interval = 100;
    private long minValue = 0;
    private String keyName;
    private String tableName = "sys_unique_key";
    private ConnectionFactory connectionFactory = null;

    public GeneralKeyGenerator(String keyName) {
        this.keyName = keyName;
    }

    public void setConnectionFactory(ConnectionFactory connectionFactory) {
        this.connectionFactory = connectionFactory;
    }

    public void setTableName(String tableName) {
        this.tableName = tableName;
    }

    public void setMinValue(long minValue) {
        this.minValue = minValue;
    }

    public void setInterval(int interval) {
        this.interval = interval;
    }

    /**
     * 获取下一个唯一 ID
     *
     * @return ID
     */
    public long nextKey() {
        if (keys == null) {
            this.apply();
        }
        while (true) {
            Keys keys = this.keys;
            AtomicLong value = keys.getValue();
            long end = keys.getEnd();
            long nowValue = value.get();
            if (nowValue > end) {
                apply();
                continue;
            }
            if (value.compareAndSet(nowValue, nowValue + 1)) {
                return nowValue;
            }
        }
    }

    private void apply() {
        Keys preKeys = keys;
        synchronized (this) {
            if (preKeys == keys) {
                this.applyForKeys();
            }
        }
    }

    private void applyForKeys() {
        Connection connection = null;
        try {
            // 获取一个新的连接
            connection = connectionFactory.createConnection();
            KeyInfo info = this.selectKey(connection);
            if (info == null) {
                info = this.insertAndGet(connection);
            }
            long value = Long.parseLong(info.getValue());
            long endValue = value + interval;
            int updated = update(value, endValue, connection);
            if (updated > 0) {
                this.keys = new Keys(new AtomicLong(value), endValue - 1);
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (SQLException e) {
            throw new RuntimeException("open connection error when apply keys", e);
        } finally {
            closeConnQuietly(connection);
        }
    }

    /**
     * 通过链接获取唯一键
     * @param connection connection
     * @return KeyInfo
     */
    private KeyInfo selectKey(Connection connection) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = connection.prepareStatement(getSelectSql());
            ps.setString(1, keyName);
            rs = ps.executeQuery();
            if (!rs.next()) {
                return null;
            }
            KeyInfo info = new KeyInfo();
            info.setValue(rs.getString("key_value"));
            return info;
        } catch (SQLException e) {
            throw new RuntimeException("select key error from key_info with name : " + keyName, e);
        } finally {
            closeResultSetQuietly(rs);
            closeStatementQuietly(ps);
        }
    }

    /**
     * 插入并且查询出
     * 假如存在数据，则返回最新的值，
     * 假如不存在数据，则插入，并且返回最新的值
     *
     * @return
     */
    private KeyInfo insertAndGet(Connection conn) {
        PreparedStatement ps = null;
        try {
            ps = conn.prepareStatement(getInsertSql());
            ps.setString(1, keyName);
            ps.setString(2, String.valueOf(minValue));
            ps.executeUpdate();
            KeyInfo keyInfo = new KeyInfo();
            keyInfo.setValue(String.valueOf(minValue));
            return keyInfo;
        } catch (SQLException e) {
            if (DbUtils.isDupUniqueKeyException(e)) {
                return this.selectKey(conn);
            }
            throw new RuntimeException("insert into key_info error with name : " + keyName + " and value : " + minValue, e);
        }
    }

    /**
     * 更新
     *
     * @param preValue
     * @param afterValue
     * @param conn
     * @return
     */
    private int update(long preValue, long afterValue, Connection conn) {
        PreparedStatement ps = null;
        try {
            ps = conn.prepareStatement(getUpdateSql());
            ps.setString(1, String.valueOf(afterValue));
            ps.setString(2, keyName);
            ps.setString(3, String.valueOf(preValue));
            return ps.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException("update key_info error with name : " + keyName + " and preValue : " + preValue + " afterValue : " + afterValue);
        } finally {
            closeStatementQuietly(ps);
        }
    }

    /**
     * 获取查询语句
     *
     * @return select key_value from where key_name = ?
     */
    private String getSelectSql() {
        StringBuilder sb = new StringBuilder();
        sb.append("select key_value from ")
                .append(tableName)
                .append(" where key_name = ?");
        return sb.toString();
    }

    /**
     * 获取插入语句
     *
     * @return insert into tableName(key_name, key_value) values(?,?)
     */
    private String getInsertSql() {
        StringBuilder sb = new StringBuilder();
        sb.append("insert into ")
                .append(tableName)
                .append("(key_name, key_value)")
                .append("values(?,?)");
        return sb.toString();
    }

    /**
     * 获取更新语句
     *
     * @return update tableName set key_value = ? where key_name = ? and key_value = ?
     */
    private String getUpdateSql() {
        StringBuilder sb = new StringBuilder();
        sb.append("update ")
                .append(tableName)
                .append(" set key_value = ? ")
                .append(" where key_name = ? and key_value = ?");
        return sb.toString();
    }

    /**
     * 关闭数据库链接
     *
     * @param conn conn
     */
    private static void closeConnQuietly(Connection conn) {
        if (conn == null) {
            return;
        }
        try {
            conn.close();
        } catch (SQLException e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 关闭 Statement
     *
     * @param st st
     */
    private static void closeStatementQuietly(Statement st) {
        if (st == null) {
            return;
        }
        try {
            st.close();
        } catch (SQLException e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 关闭结果集
     *
     * @param rs ResultSet
     */
    private static void closeResultSetQuietly(ResultSet rs) {
        if (rs == null) {
            return;
        }
        try {
            rs.close();
        } catch (SQLException e) {
            log.error(e.getMessage(), e);
        }
    }
}